package com.flute.icrawler.htmlparser.http;

import com.flute.icrawler.htmlparser.HtmlParserException;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import javax.net.ssl.SSLHandshakeException;
import javax.xml.xpath.*;

import org.apache.http.*;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import org.cyberneko.html.parsers.DOMParser;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;

public class HttpRequest {
	private class RetryHandler implements HttpRequestRetryHandler {

		public boolean retryRequest(IOException exception, int executionCount,
				HttpContext context) {
			if (executionCount >= 5)
				return false;
			if (exception instanceof NoHttpResponseException)
				return true;
			if (exception instanceof SSLHandshakeException)
				return false;
			HttpRequest request = (HttpRequest) context
					.getAttribute("http.request");
			boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
			return idempotent;
		}

		final HttpRequest httpRequest;

		private RetryHandler() {
			super();
			httpRequest = HttpRequest.this;

		}

		RetryHandler(RetryHandler retryhandler) {
			this();
		}
	}

	public HttpRequest() {
		httpclient = null;
	}

	public InputStream getHttpResponseStream(String url)
			throws HtmlParserException {
		try {
			if (!url.startsWith("http"))
				url = (new StringBuilder("http://")).append(url).toString();
			URL u = new URL(url);
			return getResponseStream(u, null);
		} catch (Exception e) {
			throw new HtmlParserException(e);
		}
	}

	public InputStream getHttpResponseStream(String url, HashMap params)
			throws HtmlParserException {
		try {
			if (!url.startsWith("http"))
				url = (new StringBuilder("http://")).append(url).toString();
			List nvps = new ArrayList();
			java.util.Map.Entry entry;
			for (Iterator it = params.entrySet().iterator(); it.hasNext(); nvps
					.add(new BasicNameValuePair((String) entry.getKey(),
							(String) entry.getValue())))
				entry = (java.util.Map.Entry) it.next();

			URL u = new URL(url);
			return getResponseStream(u, nvps);
		} catch (Exception e) {
			throw new HtmlParserException(e);
		}
	}

	public void shutdown() {
		try {
			httpclient.getConnectionManager().shutdown();
		} catch (Exception e) {
			logger.error(e);
		}
	}

	private InputStream getResponseStream(URL url, List nvps)
			throws HtmlParserException {
		System.setProperty("org.apache.commons.logging.Log",
				"org.apache.commons.logging.impl.NoOpLog");
		HttpContext context;
		try {
			httpclient = new DefaultHttpClient();
			httpclient.setHttpRequestRetryHandler(new RetryHandler(null));
			HttpParams httpparams = httpclient.getParams();
			httpparams.setParameter("http.connection.timeout",
					Integer.valueOf(30000));
			httpparams.setParameter("http.protocol.reject-relative-redirect",
					Boolean.valueOf(true));
			httpparams.setParameter("http.protocol.allow-circular-redirects",
					Boolean.valueOf(false));
			httpparams.setParameter("http.protocol.handle-redirects",
					Boolean.valueOf(true));
			httpparams.setParameter("http.protocol.cookie-policy",
					"compatibility");
			httpparams.setParameter("http.protocol .single-cookie-header",
					Boolean.valueOf(true));
			httpparams.setParameter("http.protocol .content-charset", "GBK");
			httpparams.setParameter("http.useragent", USERAGENT);
			context = new BasicHttpContext();
			if (nvps != null && nvps.size() > 0)
				return doPostRequest(httpclient, context, url, nvps);
		} catch (Exception e) {
			throw new HtmlParserException(e);
		}
		return doGetRequest(httpclient, context, url);
	}

	private InputStream doPostRequest(DefaultHttpClient httpclient,
			HttpContext context, URL url, List nvps) throws HtmlParserException {
		int status;
		String content = DEFAULTCHARSET;
		HttpPost httppost = new HttpPost(url.toString());
		if (nvps != null && nvps.size() > 0)
			try {
				httppost.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
			} catch (UnsupportedEncodingException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		HttpResponse response = null;
		try {
			response = httpclient.execute(httppost, context);
			status = response.getStatusLine().getStatusCode();
			if (status != 200)
				throw new HtmlParserException((new StringBuilder(
						"http status error!")).append(status).toString());
		} catch (ClientProtocolException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		String charset = EntityUtils.getContentCharSet(response.getEntity());
		if (charset == null)
			charset = "GBK";
		try {
			content = EntityUtils.toString(response.getEntity(), charset);
		} catch (ParseException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		String uri = null;
		if ((uri = getRedirectUrl(content)) != null)
			try {
				return doPostRequest(httpclient, context, new URL(uri), nvps);
			} catch (MalformedURLException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		try {
			return new ByteArrayInputStream(content.getBytes("utf-8"));
		} catch (Exception e) {
			throw new HtmlParserException((new StringBuilder()).append(e)
					.append(url.toString()).toString());
		}

	}

	private InputStream doGetRequest(DefaultHttpClient httpclient,
			HttpContext context, URL url) throws HtmlParserException {
		HttpResponse response = null;
		int status;
		try {
			response = httpclient.execute(new HttpGet(url.toString()), context);
		} catch (ClientProtocolException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		status = response.getStatusLine().getStatusCode();
		if (status != 200)
			throw new HtmlParserException((new StringBuilder(
					"http status error!")).append(status).toString());
		String contentType = response.getEntity().getContentType().getValue()
				.toLowerCase();
		if (contentType != null
				&& (contentType.contains("text/html") || contentType
						.contains("html/text"))) {
			String charset = EntityUtils
					.getContentCharSet(response.getEntity());
			if (charset == null)
				charset = "GBK";
			String content = null;
			try {
				content = EntityUtils.toString(response.getEntity(), charset);
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			try {
				return new ByteArrayInputStream(content.getBytes("utf-8"));
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		try {
			return response.getEntity().getContent();
		} catch (Exception e) {
			throw new HtmlParserException((new StringBuilder()).append(e)
					.append(url.toString()).toString());
		}

	}

	private String getRedirectUrl(String content) throws HtmlParserException {
		NodeList nodes = null;

		try {
			parser.setFeature("http://xml.org/sax/features/namespaces", false);

			parser.setProperty(
					"http://cyberneko.org/html/properties/names/elems", "upper");
			parser.setProperty(
					"http://cyberneko.org/html/properties/names/attrs", "lower");
		} catch (SAXNotRecognizedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (SAXNotSupportedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		try {
			parser.parse(new InputSource(new InputStreamReader(
					new ByteArrayInputStream(content.getBytes("utf-8")))));
		} catch (UnsupportedEncodingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (SAXException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (IOException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		Document doc = parser.getDocument();
		try {
			nodes = (NodeList) xpath
					.evaluate(
							"(//META[@http-equiv='REFRESH']|//META[@http-equiv='refresh'])/@content",
							doc, XPathConstants.NODESET);
		} catch (XPathExpressionException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		if (nodes == null || nodes.getLength() == 0)
			return null;
		try {
			String nodevalue = nodes.item(0).getNodeValue();
			return nodevalue.substring(nodevalue.indexOf("=") + 1);
		} catch (Exception e) {
			throw new HtmlParserException(e);
		}
	}

	private static Logger logger = Logger
			.getLogger(HttpRequest.class.getName());
	private static final String DEFAULTCHARSET = "GBK";
	private static final String USERAGENT = "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.9) Gecko/20100315 Firefox/3.5.9";
	private static XPath xpath = XPathFactory.newInstance().newXPath();
	private static DOMParser parser = new DOMParser();
	private DefaultHttpClient httpclient;

}